package com.rengwuxian.materialedittext;

import com.rengwuxian.materialedittext.validation.METLengthChecker;
import com.rengwuxian.materialedittext.validation.METValidator;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.media.image.PixelMap;
import ohos.media.image.common.AlphaType;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class MaterialEditText extends PositionLayout {
    public static final int FLOATING_LABEL_NONE = 0;
    public static final int FLOATING_LABEL_NORMAL = 1;
    public static final int FLOATING_LABEL_HIGHLIGHT = 2;

    private int mTextSize;
    private int mCursorColor;

    /**
     * the floating label's text size.
     */
    private int floatingLabelTextSize;

    /**
     * the floating label's text color.
     */
    private int floatingLabelTextColor;

    /**
     * the bottom texts' size.
     */
    private int bottomTextSize;

    /**
     * the spacing between the main text and the floating label.
     */
    private int floatingLabelPadding;

    /**
     * the spacing between the main text and the bottom components (bottom ellipsis, helper/error text, characters counter).
     */
    private int bottomSpacing;

    /**
     * whether the floating label should be shown. default is false.
     */
    private boolean floatingLabelEnabled;

    /**
     * whether to highlight the floating label's text color when focused (with the main color). default is true.
     */
    private boolean highlightFloatingLabel;

    /**
     * the base color of the line and the texts. default is black.
     */
    private int baseColor;

    /**
     * the underline's highlight color, and the highlight color of the floating label if app:highlightFloatingLabel is set true in the xml. default is black(when app:darkTheme is false) or white(when app:darkTheme is true)
     */
    private int primaryColor;

    /**
     * the color for when something is wrong.(e.g. exceeding max characters)
     */
    private int errorColor;

    /**
     * min characters count limit. 0 means no limit. default is 0. NOTE: the character counter will increase the View's height.
     */
    private int minCharacters;

    /**
     * max characters count limit. 0 means no limit. default is 0. NOTE: the character counter will increase the View's height.
     */
    private int maxCharacters;

    /**
     * whether to show the bottom ellipsis in singleLine mode. default is false. NOTE: the bottom ellipsis will increase the View's height.
     */
    private boolean singleLineEllipsis;

    /**
     * Always show the floating label, instead of animating it in/out. False by default.
     */
    private boolean floatingLabelAlwaysShown;

    /**
     * Always show the helper text, no matter if the edit text is focused. False by default.
     */
    private boolean helperTextAlwaysShown;

    /**
     * bottom ellipsis's height
     */
    private int bottomEllipsisSize;

    /**
     * Helper text at the bottom
     */
    private String helperText;

    /**
     * Helper text color
     */
    private int helperTextColor = -1;

    /**
     */
    private String tempErrorText;

    /**
     * The font used for the accent texts (floating label, error/helper text, character counter, etc.)
     */
    private Font accentTypeface;

    /**
     * The font used on the view (EditText content)
     */
    private Font typeface;

    /**
     * Text for the floatLabel if different from the hint
     */
    private String floatingLabelText;

    /**
     * Whether or not to show the underline. Shown by default
     */
    private boolean hideUnderline;

    /**
     * Underline's color
     */
    private int underlineColor;

    /**
     * Whether the characters count is valid
     */
    private boolean charactersCountValid;

    /**
     * Whether check the characters count at the beginning it's shown.
     */
    private boolean checkCharactersCountAtBeginning;

    /**
     * Left Icon
     */
    private PixelMap[] iconLeftBitmaps;

    /**
     * Right Icon
     */
    private PixelMap[] iconRightBitmaps;

    /**
     * Clear Button
     */
    private PixelMap[] clearButtonBitmaps;


    private boolean showClearButton;
    private int iconSize;
    private int leftIconWidth;
    private int leftIconHeight;
    private int rightIconWidth;
    private int rightIconHeight;

    private int leftIconMarginText;
    private int leftIconMarginBottom;
    private int rightIconMarginText;
    private int rightIconMarginBottom;

    private ColorStateList textColorStateList;
    private ColorStateList textColorHintStateList;

    private Paint paint = new Paint();
    private Paint textPaint = new Paint();
    private Paint bitmapPaint = new Paint();
    private Paint dddPaint = new Paint();
    private TextField textField;

    private Image leftIcon;
    private Image rightIcon;
    private Image underline;
    private Image clear;
    private Text helper;
    private Text limit;
    private Text floating;
    private Image ddd;

    public MaterialEditText(Context context) {
        this(context, null, null);
    }

    public MaterialEditText(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public MaterialEditText(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(attrs);
    }

    private void initComponent(){
        Component component =
                LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_materialedittext, null, false);
        addComponent(component);
        textField = (TextField) component.findComponentById(ResourceTable.Id_met_edittext);
        leftIcon = (Image) component.findComponentById(ResourceTable.Id_met_lefticon);
        rightIcon = (Image) component.findComponentById(ResourceTable.Id_met_righticon);
        underline = (Image) component.findComponentById(ResourceTable.Id_met_underline);
        clear = (Image) component.findComponentById(ResourceTable.Id_met_clear);
        helper = (Text) component.findComponentById(ResourceTable.Id_met_helper);
        limit = (Text) component.findComponentById(ResourceTable.Id_met_limit);
        floating = (Text) component.findComponentById(ResourceTable.Id_met_floating);
        ddd = (Image) component.findComponentById(ResourceTable.Id_met_ddd);
        paint.setAntiAlias(true);
        textPaint.setAntiAlias(true);
        bitmapPaint.setAntiAlias(true);
    }

    private void initAttrSet(AttrSet attrs){
        iconSize = getPixel(32);

        bottomSpacing = Density.dp2px(getContext(), 8);
        bottomEllipsisSize = Density.dp2px(getContext(), 4);

        // default baseColor is black
        int defaultBaseColor = Color.BLACK.getValue();

        boolean enabled = AttrUtils.getBooleanFromAttr(attrs, "met_enabled", true);
        textField.setEnabled(enabled);

        mTextSize = AttrUtils.getDimensionFromAttr(attrs, "met_textSize", getPixel(15));

        String metText = AttrUtils.getStringFromAttr(attrs, "met_text", "");
        String metHint = AttrUtils.getStringFromAttr(attrs, "met_hint", "  ");

        textField.setText(metText);
        textField.setHint(metHint);

        int metTextColor = AttrUtils.getColorFromAttr(attrs, "met_textColor", 0xff000000);
        int metTextColorDisabled = AttrUtils.getColorFromAttr(attrs, "met_textColor_disabled", 0xffb8babc);
        textColorStateList = new ColorStateList();
        textColorStateList.addState(new int[] {ComponentState.COMPONENT_STATE_DISABLED}, metTextColorDisabled);
        textColorStateList.addState(new int[] {ComponentState.COMPONENT_STATE_EMPTY}, metTextColor);

        int metTextColorHint = AttrUtils.getColorFromAttr(attrs, "met_textColorHint", 0xffb8babc);
        int metTextColorHintDisabled = AttrUtils.getColorFromAttr(attrs, "met_textColorHint_disabled", 0xffb8babc);
        textColorHintStateList = new ColorStateList();
        textColorHintStateList.addState(
                new int[] {ComponentState.COMPONENT_STATE_DISABLED}, metTextColorHintDisabled);
        textColorHintStateList.addState(new int[] {ComponentState.COMPONENT_STATE_EMPTY}, metTextColorHint);
        baseColor = AttrUtils.getColorFromAttr(attrs, "met_baseColor", defaultBaseColor);

        String metTypeface = AttrUtils.getStringFromAttr(attrs, "met_typeface", null);
        String metAccentTypeface = AttrUtils.getStringFromAttr(attrs, "met_accentTypeface", null);
        if (metTypeface != null) {
            File file = resPathToFile(getContext(), metTypeface);
            Font.Builder builder = new Font.Builder(file);
            this.typeface = builder.build();
            textField.setFont(this.typeface);
        }
        if (metAccentTypeface != null) {
            File file = resPathToFile(getContext(), metAccentTypeface);
            Font.Builder builder = new Font.Builder(file);
            this.accentTypeface = builder.build();
            helper.setFont(this.accentTypeface);
            floating.setFont(this.accentTypeface);
            limit.setFont(this.accentTypeface);
        }

        primaryColor = AttrUtils.getColorFromAttr(attrs, "met_primaryColor", 0xff0022FF);

        mCursorColor = AttrUtils.getColorFromAttr(attrs, "met_cursorColor", 0xff00ffff);
        setCursorColor(mCursorColor);

        String mode = AttrUtils.getStringFromAttr(attrs, "met_floatingLabel", null);
        if ("normal".equals(mode)) {
            setFloatingLabelInternal(FLOATING_LABEL_NORMAL);
        } else if ("highlight".equals(mode)) {
            setFloatingLabelInternal(FLOATING_LABEL_HIGHLIGHT);
        } else {
            setFloatingLabelInternal(FLOATING_LABEL_NONE);
        }
        errorColor = AttrUtils.getColorFromAttr(attrs, "met_errorColor", 0xffe7492e);

        minCharacters = AttrUtils.getIntFromAttr(attrs, "met_minCharacters", 0);
        maxCharacters = AttrUtils.getIntFromAttr(attrs, "met_maxCharacters", 0);
        singleLineEllipsis = AttrUtils.getBooleanFromAttr(attrs, "met_singleLineEllipsis", false);
        helperText = AttrUtils.getStringFromAttr(attrs, "met_helperText", " ");
        helperTextColor = AttrUtils.getColorFromAttr(attrs, "met_helperTextColor", -1);
        bottomSpacing = AttrUtils.getDimensionFromAttr(attrs, "met_bottomSpacing", getPixel(5));

        floatingLabelText = AttrUtils.getStringFromAttr(attrs, "met_floatingLabelText", metHint);
        floatingLabelPadding = AttrUtils.getDimensionFromAttr(attrs, "met_floatingLabelPadding", bottomSpacing);
        floatingLabelTextSize =
                AttrUtils.getDimensionFromAttr(attrs, "met_floatingLabelTextSize", Density.dp2px(getContext(), 12));
        floatingLabelTextColor = AttrUtils.getColorFromAttr(attrs, "met_floatingLabelTextColor", -1);
        bottomTextSize = AttrUtils.getDimensionFromAttr(attrs, "met_bottomTextSize", Density.dp2px(getContext(), 12));
        hideUnderline = AttrUtils.getBooleanFromAttr(attrs, "met_hideUnderline", false);
        underlineColor = AttrUtils.getColorFromAttr(attrs, "met_underlineColor", -1);

        leftIconWidth = AttrUtils.getDimensionFromAttr(attrs, "met_iconLeftWidth", iconSize);
        leftIconHeight = AttrUtils.getDimensionFromAttr(attrs, "met_iconLeftHeight", iconSize);
        rightIconWidth = AttrUtils.getDimensionFromAttr(attrs, "met_iconRightWidth", iconSize);
        rightIconHeight = AttrUtils.getDimensionFromAttr(attrs, "met_iconRightHeight", iconSize);
        leftIconMarginText = AttrUtils.getDimensionFromAttr(attrs, "met_iconLeftMarginText", getPixel(10));
        rightIconMarginText = AttrUtils.getDimensionFromAttr(attrs, "met_iconRightMarginText", getPixel(10));

        // icon底部默认与文字最后一行底部对齐
        leftIconMarginBottom = AttrUtils.getDimensionFromAttr(attrs, "met_iconLeftMarginBottom", 0);
        rightIconMarginBottom = AttrUtils.getDimensionFromAttr(attrs, "met_iconRightMarginBottom", 0);

        Element leftElement = AttrUtils.getElementFromAttr(attrs, "met_iconLeft", null);
        iconLeftBitmaps = generateIconBitmaps(leftElement, leftIconWidth, leftIconHeight);
        Element rightElement = AttrUtils.getElementFromAttr(attrs, "met_iconRight", null);
        iconRightBitmaps = generateIconBitmaps(rightElement, rightIconWidth, rightIconHeight);

        showClearButton = AttrUtils.getBooleanFromAttr(attrs, "met_clearButton", false);
        clearButtonBitmaps = generateIconBitmaps(ResourceTable.Media_met_ic_clear);
        floatingLabelAlwaysShown = AttrUtils.getBooleanFromAttr(attrs, "met_floatingLabelAlwaysShown", false);
        helperTextAlwaysShown = AttrUtils.getBooleanFromAttr(attrs, "met_helperTextAlwaysShown", false);
        checkCharactersCountAtBeginning =
                AttrUtils.getBooleanFromAttr(attrs, "met_checkCharactersCountAtBeginning", true);

        if (!checkCharactersCountAtBeginning) {
            charactersCountValid = true;
        } else {
            charactersCountValid = true;
            if (minCharacters > 0) {
                if (metText.length() < minCharacters) {
                    charactersCountValid = false;
                }
            }
            if (maxCharacters > 0) {
                if (metText.length() > maxCharacters) {
                    charactersCountValid = false;
                }
            }
        }

        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }

        if (bottomSpacing != 0) {
            LayoutConfig layoutConfig = underline.getLayoutConfig();
            layoutConfig.setMarginTop(bottomSpacing);
            underline.setLayoutConfig(layoutConfig);
        }

        if (floatingLabelPadding != 0) {
            LayoutConfig layoutConfig = floating.getLayoutConfig();
            layoutConfig.setMarginBottom(floatingLabelPadding);
            floating.setLayoutConfig(layoutConfig);
        }
    }


    private void initComponentAction(){
        textField.setComponentStateChangedListener(
                new ComponentStateChangedListener() {
                    @Override
                    public void onComponentStateChanged(Component component, int i) {
                        refresh();
                    }
                });

        textField.setFocusChangedListener(
                new FocusChangedListener() {
                    @Override
                    public void onFocusChange(Component component, boolean b) {
                        refresh();
                    }
                });

        ddd.addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        if (singleLineEllipsis && textField.hasFocus()) {
                            dddPaint.setTextSize(mTextSize);
                            float slength = dddPaint.measureText(textField.getText());
                            int tlength = textField.getWidth();
                            if (slength > tlength) {
                                dddPaint.setColor(new Color(isInternalValid() ? primaryColor : errorColor));
                                int startY = bottomSpacing;
                                int ellipsisStartX = 0;
                                int signum = 1;
                                canvas.drawCircle(
                                        ellipsisStartX + signum * bottomEllipsisSize / 2,
                                        startY + bottomEllipsisSize / 2,
                                        bottomEllipsisSize / 2,
                                        dddPaint);
                                canvas.drawCircle(
                                        ellipsisStartX + signum * bottomEllipsisSize * 5 / 2,
                                        startY + bottomEllipsisSize / 2,
                                        bottomEllipsisSize / 2,
                                        dddPaint);
                                canvas.drawCircle(
                                        ellipsisStartX + signum * bottomEllipsisSize * 9 / 2,
                                        startY + bottomEllipsisSize / 2,
                                        bottomEllipsisSize / 2,
                                        dddPaint);
                            }
                        }
                    }
                });

        underline.addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        if (!hideUnderline) {
                            int lineStartY = 0;
                            int startX = 0;
                            int endX = underline.getWidth();
                            if (!isInternalValid()) { // not valid
                                paint.setColor(new Color(errorColor));
                                canvas.drawRect(
                                        new RectFloat(startX, lineStartY, endX, lineStartY + getPixel(2)), paint);
                            } else if (!textField.isEnabled()) { // disabled
                                paint.setColor(
                                        new Color(
                                                underlineColor != -1
                                                        ? underlineColor
                                                        : baseColor & 0x00ffffff | 0x44000000));
                                float interval = getPixel(1);
                                for (int xOffset = 0; xOffset < getWidth(); xOffset += interval * 3) {
                                    canvas.drawRect(
                                            new RectFloat(
                                                    startX + xOffset,
                                                    lineStartY,
                                                    startX + xOffset + interval,
                                                    lineStartY + getPixel(1)),
                                            paint);
                                }
                            } else if (textField.hasFocus()) { // focused
                                paint.setColor(new Color(primaryColor));
                                canvas.drawRect(
                                        new RectFloat(startX, lineStartY, endX, lineStartY + getPixel(2)), paint);
                            } else { // normal
                                paint.setColor(
                                        new Color(
                                                underlineColor != -1
                                                        ? underlineColor
                                                        : baseColor & 0x00ffffff | 0x1E000000));
                                canvas.drawRect(
                                        new RectFloat(startX, lineStartY, endX, lineStartY + getPixel(1)), paint);
                            }
                        }
                    }
                });

        clear.setClickedListener(
                new ClickedListener() {
                    @Override
                    public void onClick(Component component) {
                        textField.setText("");
                    }
                });

        textField.addTextObserver(
                new Text.TextObserver() {
                    @Override
                    public void onTextUpdated(String s, int i, int i1, int i2) {
                        tempErrorText = "";
                        if (minCharacters > 0) {
                            if (s.length() < minCharacters) {
                                charactersCountValid = false;
                                refresh();
                                return;
                            }
                        }

                        if (maxCharacters > 0) {
                            if (s.length() > maxCharacters) {
                                charactersCountValid = false;
                                refresh();
                                return;
                            }
                        }

                        charactersCountValid = true;
                        refresh();
                    }
                });

        leftIcon.addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        if (iconLeftBitmaps != null) {
                            PixelMap pixelMap =
                                    iconLeftBitmaps[
                                            !isInternalValid()
                                                    ? 3
                                                    : !textField.isEnabled() ? 2 : textField.hasFocus() ? 1 : 0];
                            int top =
                                    textField.getHeight()
                                            + floating.getHeight()
                                            - pixelMap.getImageInfo().size.height
                                            - leftIconMarginBottom
                                            + floatingLabelPadding;
                            if (top < 0) {
                                top = 0;
                            }
                            canvas.drawPixelMapHolder(new PixelMapHolder(pixelMap), 0, top, bitmapPaint);
                        }
                    }
                });
        rightIcon.addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        if (iconRightBitmaps != null) {
                            PixelMap pixelMap =
                                    iconRightBitmaps[
                                            !isInternalValid()
                                                    ? 3
                                                    : !textField.isEnabled() ? 2 : textField.hasFocus() ? 1 : 0];
                            int top =
                                    textField.getHeight()
                                            + floating.getHeight()
                                            - pixelMap.getImageInfo().size.height
                                            - rightIconMarginBottom
                                            + floatingLabelPadding;
                            if (top < 0) {
                                top = 0;
                            }
                            canvas.drawPixelMapHolder(new PixelMapHolder(pixelMap), 0, top, bitmapPaint);
                        }
                    }
                });
    }

    private void init(AttrSet attrs) {
        initComponent();
        initAttrSet(attrs);
        initComponentAction();
        refresh();
    }

    private File resPathToFile(Context context, String resPath) {
        String[] fileNames = resPath.split(File.separator);
        File tempFile = new File(context.getCacheDir() + File.separator + fileNames[fileNames.length - 1]);
        FileOutputStream fos = null;
        try {
            ResourceManager resourceManager = context.getResourceManager();
            Resource resource = resourceManager.getRawFileEntry(resPath).openRawFile();
            byte[] buffer = new byte[resource.available()];
            resource.read(buffer);
            fos = new FileOutputStream(tempFile);
            fos.write(buffer);
        } catch (Exception o) {
            tempFile = null;
        } finally {
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    fos = null;
                }
            }
        }
        return tempFile;
    }

    private void refreshTextField(){
        textField.setTextSize(mTextSize);
        if (singleLineEllipsis) {
            textField.setMultipleLine(false);
            textField.setMaxTextLines(1);
        } else {
            textField.setMultipleLine(true);
            textField.setMaxTextLines(255);
        }
        if (textField.isEnabled()) {
            textField.setTextColor(
                    new Color(
                            textColorStateList.getColorForState(
                                    new int[] {ComponentState.COMPONENT_STATE_EMPTY}, 0xffFFA3ED)));
            textField.setHintColor(
                    new Color(
                            textColorHintStateList.getColorForState(
                                    new int[] {ComponentState.COMPONENT_STATE_EMPTY}, 0xff00ffaa)));
        } else {
            textField.setTextColor(
                    new Color(
                            textColorStateList.getColorForState(
                                    new int[] {ComponentState.COMPONENT_STATE_DISABLED}, 0xffb8babc)));
            textField.setHintColor(
                    new Color(
                            textColorHintStateList.getColorForState(
                                    new int[] {ComponentState.COMPONENT_STATE_DISABLED}, 0xffb8babc)));
        }
    }

    private void refreshIcon(){
        if (iconLeftBitmaps != null) {
            leftIcon.setVisibility(Component.VISIBLE);
            leftIcon.invalidate();
        } else {
            leftIcon.setVisibility(Component.HIDE);
        }
        if (iconRightBitmaps != null) {
            rightIcon.setVisibility(Component.VISIBLE);
            rightIcon.invalidate();
        } else {
            rightIcon.setVisibility(Component.HIDE);
        }
    }

    private void refreshClear(){
        if (textField.hasFocus()
                && showClearButton
                && textField.getText() != null
                && textField.getText().length() > 0
                && isEnabled()) {
            clear.setImageElement(new PixelMapElement(clearButtonBitmaps[0]));
            clear.setVisibility(Component.VISIBLE);
        } else if (showClearButton) {
            clear.setVisibility(Component.INVISIBLE);
        } else {
            clear.setVisibility(Component.HIDE);
        }
    }

    private void refreshError(){
        if (tempErrorText != null && tempErrorText.length() > 0 && !isInternalValid()) {
            helper.setText(tempErrorText);
            helper.setTextSize(bottomTextSize);
            helper.setTextColor(new Color(errorColor));
        } else if ((textField.hasFocus() || helperTextAlwaysShown) && helperText != null && helperText.length() > 0) {
            helper.setText(helperText);
            helper.setTextSize(bottomTextSize);
            helper.setTextColor(new Color(helperTextColor));
        } else {
            helper.setText(" ");
        }
    }

    private void refreshLimit(){
        if ((textField.hasFocus() || !isCharactersCountValid()) && hasCharactersCounter()) {
            limit.setVisibility(Component.VISIBLE);
            limit.setTextSize(bottomTextSize);
            limit.setTextColor(
                    new Color(isCharactersCountValid() ? (baseColor & 0x00ffffff | 0x44000000) : errorColor));
            limit.setText(getCharactersCounterText());
        } else {
            limit.setVisibility(Component.HIDE);
        }
    }

    private void refreshFloat(){
        if (floatingLabelEnabled && floatingLabelText != null && floatingLabelText.length() > 0) {
            floating.setVisibility(Component.VISIBLE);
            floating.setTextSize(floatingLabelTextSize);
            if (floatingLabelAlwaysShown || (textField.getText() != null && textField.getText().length() > 0)) {
                floating.setText(floatingLabelText);
            } else {
                floating.setText(" ");
            }
            if (highlightFloatingLabel && textField.hasFocus()) {
                floating.setTextColor(new Color(primaryColor));
            } else {
                floating.setTextColor(
                        new Color(
                                floatingLabelTextColor != -1
                                        ? floatingLabelTextColor
                                        : (baseColor & 0x00ffffff | 0x44000000)));
            }
        } else {
            floating.setVisibility(Component.HIDE);
        }
    }

    private void refresh() {
        refreshTextField();
        refreshIcon();
        refreshClear();
        refreshError();
        refreshLimit();
        refreshFloat();

        if (singleLineEllipsis) {
            ddd.setVisibility(Component.VISIBLE);
            ddd.invalidate();
        } else {
            ddd.setVisibility(Component.HIDE);
        }

        underline.invalidate();
    }

    private int getPixel(int dp) {
        return Density.dp2px(getContext(), dp);
    }

    private void setFloatingLabelInternal(int mode) {
        switch (mode) {
            case FLOATING_LABEL_NORMAL:
                floatingLabelEnabled = true;
                highlightFloatingLabel = false;
                break;
            case FLOATING_LABEL_HIGHLIGHT:
                floatingLabelEnabled = true;
                highlightFloatingLabel = true;
                break;
            default:
                floatingLabelEnabled = false;
                highlightFloatingLabel = false;
                break;
        }
    }

    private PixelMap[] generateIconBitmaps(int origin) {
        if (origin == -1) {
            return null;
        }
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), origin);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return generateIconBitmaps(pixelMap);
    }

    private PixelMap[] generateIconBitmaps(Element drawable, int width, int height) {
        if (drawable == null) {
            return null;
        }
        PixelMap bitmap = drawableToPixelmap(drawable);
        Canvas canvas = new Canvas(new Texture(bitmap));
        drawable.setBounds(0, 0, bitmap.getImageInfo().size.width, bitmap.getImageInfo().size.height);
        drawable.drawToCanvas(canvas);
        return generateIconBitmaps(copy(bitmap, width, height));
    }

    private PixelMap drawableToPixelmap(Element drawable) {
        if (drawable instanceof Element) {
            return ((PixelMapElement) drawable).getPixelMap();
        }
        return null;
    }

    private PixelMap copy(PixelMap source, int width, int height) {
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        initializationOptions.alphaType = AlphaType.PREMUL;
        initializationOptions.size = new Size(width, height);
        return PixelMap.create(source, initializationOptions);
    }

    private PixelMap[] generateIconBitmaps(PixelMap origin) {
        if (origin == null) {
            return null;
        }
        PixelMap[] iconBitmaps = new PixelMap[4];
        origin = scaleIcon(origin);
        iconBitmaps[0] = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height);
        Canvas canvas = new Canvas(new Texture(iconBitmaps[0]));
        canvas.drawColor(
                baseColor & 0x00ffffff | (Colors.isLight(baseColor) ? 0xff000000 : 0x8a000000),
                Canvas.PorterDuffMode.SRC_IN);
        iconBitmaps[1] = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height);
        canvas = new Canvas(new Texture(iconBitmaps[1]));
        canvas.drawColor(primaryColor, Canvas.PorterDuffMode.SRC_IN);
        iconBitmaps[2] = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height);
        canvas = new Canvas(new Texture(iconBitmaps[2]));
        canvas.drawColor(
                baseColor & 0x00ffffff | (Colors.isLight(baseColor) ? 0x4c000000 : 0x42000000),
                Canvas.PorterDuffMode.SRC_IN);
        iconBitmaps[3] = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height);
        canvas = new Canvas(new Texture(iconBitmaps[3]));
        canvas.drawColor(errorColor, Canvas.PorterDuffMode.SRC_IN);
        return iconBitmaps;
    }

    private PixelMap scaleIcon(PixelMap origin) {
        int width = origin.getImageInfo().size.width;
        int height = origin.getImageInfo().size.height;
        int size = Math.max(width, height);
        if (size == iconSize) {
            return origin;
        } else if (size > iconSize) {
            int scaledWidth;
            int scaledHeight;
            if (width > iconSize) {
                scaledWidth = iconSize;
                scaledHeight = (int) (iconSize * ((float) height / width));
            } else {
                scaledHeight = iconSize;
                scaledWidth = (int) (iconSize * ((float) width / height));
            }
            return copy(origin, scaledWidth, scaledHeight);
        } else {
            return origin;
        }
    }

    public void setIconLeft(int res) {
        iconLeftBitmaps = generateIconBitmaps(res);
        refresh();
    }

    public void setIconLeft(Element element) {
        iconLeftBitmaps = generateIconBitmaps(drawableToPixelmap(element));
        refresh();
    }

    public void setIconLeft(PixelMap pixelMap) {
        iconLeftBitmaps = generateIconBitmaps(pixelMap);
        refresh();
    }

    public void setIconRight(int res) {
        iconRightBitmaps = generateIconBitmaps(res);
        refresh();
    }

    public void setIconRight(Element element) {
        iconRightBitmaps = generateIconBitmaps(drawableToPixelmap(element));
        refresh();
    }

    public void setIconRight(PixelMap pixelMap) {
        iconRightBitmaps = generateIconBitmaps(pixelMap);
        refresh();
    }

    public boolean isShowClearButton() {
        return showClearButton;
    }

    public void setShowClearButton(boolean show) {
        showClearButton = show;
        refresh();
    }

    public boolean isFloatingLabelAlwaysShown() {
        return floatingLabelAlwaysShown;
    }

    public void setFloatingLabelAlwaysShown(boolean floatingLabelAlwaysShown) {
        this.floatingLabelAlwaysShown = floatingLabelAlwaysShown;
        refresh();
    }

    public boolean isHelperTextAlwaysShown() {
        return helperTextAlwaysShown;
    }

    public void setHelperTextAlwaysShown(boolean helperTextAlwaysShown) {
        this.helperTextAlwaysShown = helperTextAlwaysShown;
        refresh();
    }

    public Font getAccentTypeface() {
        return accentTypeface;
    }

    public void setAccentTypeface(Font accentTypeface) {
        this.accentTypeface = accentTypeface;
        helper.setFont(this.accentTypeface);
        floating.setFont(this.accentTypeface);
        limit.setFont(this.accentTypeface);
    }

    public void setTextSize(int size) {
        mTextSize = size;
        textField.setTextSize(size);
        refresh();
    }

    public boolean isHideUnderline() {
        return hideUnderline;
    }

    public void setHideUnderline(boolean hideUnderline) {
        this.hideUnderline = hideUnderline;
        refresh();
    }

    public int getUnderlineColor() {
        return underlineColor;
    }

    public void setUnderlineColor(int color) {
        this.underlineColor = color;
        refresh();
    }

    public void setCursorColor(int color) {
        if (color == 0){
            return;
        }
        mCursorColor = color;
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(mCursorColor));
        textField.setCursorElement(shapeElement);
    }

    public void setText(String text) {
        if (text == null){
            return;
        }
        textField.setText(text);
    }

    public String getText() {
        return textField.getText();
    }

    public void setHint(String hint) {
        if (hint == null){
            return;
        }
        textField.setHint(hint);
    }

    public String getHint() {
        return textField.getHint();
    }

    public void setEnabled(boolean enable) {
        textField.setEnabled(enable);
    }

    public boolean isEnabled() {
        return textField.isEnabled();
    }

    public void setError(String error) {
        tempErrorText = error;
        refresh();
    }

    public String getError() {
        return tempErrorText;
    }

    private boolean isInternalValid() {
        return (tempErrorText == null || tempErrorText.length() == 0) && isCharactersCountValid();
    }

    public boolean isCharactersCountValid() {
        return charactersCountValid;
    }

    private boolean hasCharactersCounter() {
        return minCharacters > 0 || maxCharacters > 0;
    }

    private String getCharactersCounterText() {
        String text;
        if (minCharacters <= 0) {
            text = checkLength(textField.getText()) + " / " + maxCharacters;
        } else if (maxCharacters <= 0) {
            text = checkLength(textField.getText()) + " / " + minCharacters + "+";
        } else {
            text = checkLength(textField.getText()) + " / " + minCharacters + "-" + maxCharacters;
        }
        return text;
    }

    private int checkLength(String string) {
        if (string != null) {
            return string.length();
        }
        return 0;
    }

    private List<METValidator> validators;
    private METLengthChecker lengthChecker;

    public boolean validate() {
        if (validators == null || validators.isEmpty()) {
            return true;
        }

        CharSequence text = getText();
        boolean isEmpty = text.length() == 0;

        boolean isValid = true;
        for (METValidator validator : validators) {
            isValid = isValid && validator.isValid(text, isEmpty);
            if (!isValid) {
                setError(validator.getErrorMessage());
                break;
            }
        }
        if (isValid) {
            setError(null);
        }
        textField.invalidate();
        return isValid;
    }

    public boolean hasValidators() {
        return this.validators != null && !this.validators.isEmpty();
    }

    /**
     * Adds a new validator to the View's list of validators
     * <p/>
     * This will be checked with the others in {@link #validate()}
     *
     * @param validator Validator to add
     * @return This instance, for easy chaining
     */
    public MaterialEditText addValidator(METValidator validator) {
        if (validators == null) {
            this.validators = new ArrayList<>();
        }
        this.validators.add(validator);
        return this;
    }

    public void clearValidators() {
        if (this.validators != null) {
            this.validators.clear();
        }
    }

    public List<METValidator> getValidators() {
        return this.validators;
    }

    public void setLengthChecker(METLengthChecker lengthChecker) {
        this.lengthChecker = lengthChecker;
    }

    public int getErrorColor() {
        return errorColor;
    }

    public void setErrorColor(int color) {
        errorColor = color;
        refresh();
    }

    public void setHelperText(String helperText) {
        this.helperText = helperText;
        refresh();
    }

    public String getHelperText() {
        return helperText;
    }

    public int getHelperTextColor() {
        return helperTextColor;
    }

    public void setHelperTextColor(int color) {
        helperTextColor = color;
        refresh();
    }

    public int getMaxCharacters() {
        return maxCharacters;
    }

    public void setMaxCharacters(int max) {
        maxCharacters = max;
        refresh();
    }

    public int getMinCharacters() {
        return minCharacters;
    }

    public void setMinCharacters(int min) {
        minCharacters = min;
        refresh();
    }

    public void setSingleLineEllipsis() {
        setSingleLineEllipsis(true);
    }

    public void setSingleLineEllipsis(boolean enabled) {
        singleLineEllipsis = enabled;
        if (singleLineEllipsis) {
            ddd.setVisibility(Component.VISIBLE);
            ddd.invalidate();
        } else {
            ddd.setVisibility(Component.HIDE);
        }
        refresh();
    }

    public void setFloatingLabel(int mode) {
        setFloatingLabelInternal(mode);
        refresh();
    }

    public int getFloatingLabelPadding() {
        return floatingLabelPadding;
    }

    public void setFloatingLabelPadding(int padding) {
        floatingLabelPadding = padding;
        LayoutConfig layoutConfig = floating.getLayoutConfig();
        layoutConfig.setMarginBottom(floatingLabelPadding);
        floating.setLayoutConfig(layoutConfig);
        refresh();
    }

    public void setBaseColor(int color) {
        if (baseColor != color) {
            baseColor = color;
        }
        refresh();
    }

    public void setPrimaryColor(int color) {
        primaryColor = color;
        refresh();
    }

    public void setMetTextColor(ColorStateList colors) {
        textColorStateList = colors;
        refresh();
    }

    public void setMetHintTextColor(ColorStateList colors) {
        textColorHintStateList = colors;
        refresh();
    }

    public int getFloatingLabelTextSize() {
        return floatingLabelTextSize;
    }

    public void setFloatingLabelTextSize(int size) {
        floatingLabelTextSize = size;
        refresh();
    }

    public int getFloatingLabelTextColor() {
        return floatingLabelTextColor;
    }

    public void setFloatingLabelTextColor(int color) {
        this.floatingLabelTextColor = color;
        refresh();
    }

    public int getBottomTextSize() {
        return bottomTextSize;
    }

    public void setBottomTextSize(int size) {
        bottomTextSize = size;
        refresh();
    }

    public String getFloatingLabelText() {
        return floatingLabelText;
    }

    public void setFloatingLabelText(String floatingLabelText) {
        this.floatingLabelText = floatingLabelText;
        refresh();
    }

    public int getIconSize() {
        return iconSize;
    }

    public void setIconSize(int iconSize) {
        this.iconSize = iconSize;
    }

    public int getLeftIconWidth() {
        return leftIconWidth;
    }

    public void setLeftIconWidth(int leftIconWidth) {
        this.leftIconWidth = leftIconWidth;
        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getLeftIconHeight() {
        return leftIconHeight;
    }

    public void setLeftIconHeight(int leftIconHeight) {
        this.leftIconHeight = leftIconHeight;
        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getRightIconWidth() {
        return rightIconWidth;
    }

    public void setRightIconWidth(int rightIconWidth) {
        this.rightIconWidth = rightIconWidth;
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getRightIconHeight() {
        return rightIconHeight;
    }

    public void setRightIconHeight(int rightIconHeight) {
        this.rightIconHeight = rightIconHeight;
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getLeftIconMarginText() {
        return leftIconMarginText;
    }

    public void setLeftIconMarginText(int leftIconMarginText) {
        this.leftIconMarginText = leftIconMarginText;
        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getLeftIconMarginBottom() {
        return leftIconMarginBottom;
    }

    public void setLeftIconMarginBottom(int leftIconMarginBottom) {
        this.leftIconMarginBottom = leftIconMarginBottom;
        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getRightIconMarginText() {
        return rightIconMarginText;
    }

    public void setRightIconMarginText(int rightIconMarginText) {
        this.rightIconMarginText = rightIconMarginText;
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }
    }

    public int getRightIconMarginBottom() {
        return rightIconMarginBottom;
    }

    public void setRightIconMarginBottom(int rightIconMarginBottom) {
        this.rightIconMarginBottom = rightIconMarginBottom;
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }
    }

    public void setLeftIcon(PixelMapElement leftElement) {
        iconLeftBitmaps = generateIconBitmaps(leftElement, leftIconWidth, leftIconHeight);
        if (iconLeftBitmaps != null) {
            LayoutConfig layoutConfig = leftIcon.getLayoutConfig();
            layoutConfig.width = iconLeftBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginRight(leftIconMarginText);
            leftIcon.setLayoutConfig(layoutConfig);
        }
        refresh();
    }

    public void setRightIcon(PixelMapElement rightElement) {
        iconRightBitmaps = generateIconBitmaps(rightElement, rightIconWidth, rightIconHeight);
        if (iconRightBitmaps != null) {
            LayoutConfig layoutConfig = rightIcon.getLayoutConfig();
            layoutConfig.width = iconRightBitmaps[0].getImageInfo().size.width;
            layoutConfig.setMarginLeft(rightIconMarginText);
            rightIcon.setLayoutConfig(layoutConfig);
        }
        refresh();
    }

    public int getBottomSpacing() {
        return bottomSpacing;
    }

    public void setBottomSpacing(int bottomSpacing) {
        this.bottomSpacing = bottomSpacing;
        LayoutConfig layoutConfig = underline.getLayoutConfig();
        layoutConfig.setMarginTop(bottomSpacing);
        underline.setLayoutConfig(layoutConfig);
        refresh();
    }
}
